﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Text;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using Salary.Sys.Core.Infrastructure;

namespace Salary.Sys.Core.Configuration
{
    public class Configuration : SingletonBase<Configuration>
    {
        private readonly ConfigurationDbContext _dbContext;
        private readonly ReaderWriterLockSlim _writeLocker = new ReaderWriterLockSlim(LockRecursionPolicy.NoRecursion);
        private readonly ILogger _logger;

        private Configuration()
        {
            _dbContext = new ConfigurationDbContext();
            _entriesDict = new Dictionary<string, Dictionary<string, string>>();
            _logger = SalarySys.Instance.GetLogger<Configuration>();
        }

        private readonly Dictionary<string, Dictionary<string, string>> _entriesDict;

        protected override bool AutoInitialization => true;

        public override void Initialize()
        {
            var migrations = _dbContext.Database.GetPendingMigrations();
            if (migrations.Any())
                _dbContext.Database.Migrate();

            foreach (var entry in _dbContext.Entries.AsNoTracking())
            {
                if (_entriesDict.TryGetValue(entry.Domain, out var value))
                {
                    value.Add(entry.Key, entry.Value);
                }
                else
                {
                    var dict = new Dictionary<string, string>
                    {
                        { entry.Key, entry.Value }
                    };
                    _entriesDict.Add(entry.Domain, dict);
                }
            }
        }

        public string? GetValue(string domain, string key)
        {
            _writeLocker.EnterReadLock();
            try
            {
                if (!_entriesDict.ContainsKey(domain) && !_entriesDict[domain].ContainsKey(key)) return null;
                return _entriesDict[domain][key];
            }
            finally
            {
                _writeLocker.ExitReadLock();
            }
        }

        public int? GetInt32(string domain, string key)
        {
            _writeLocker.EnterReadLock();
            try
            {
                if (!_entriesDict.ContainsKey(domain) && !_entriesDict[domain].ContainsKey(key)) return null;
                return Convert.ToInt32(_entriesDict[domain][key]);
            }
            finally
            {
                _writeLocker.ExitReadLock();
            }
        }

        public long? GetInt64(string domain, string key)
        {
            _writeLocker.EnterReadLock();
            try
            {
                if (!_entriesDict.ContainsKey(domain) && !_entriesDict[domain].ContainsKey(key)) return null;
                return Convert.ToInt64(_entriesDict[domain][key]);
            }
            finally
            {
                _writeLocker.ExitReadLock();
            }
        }

        public TObject? GetObject<TObject>(string domain, string key)
        {
            _writeLocker.EnterReadLock();
            try
            {
                if (!_entriesDict.ContainsKey(domain) && !_entriesDict[domain].ContainsKey(key)) return default(TObject);
                return JsonConvert.DeserializeObject<TObject>(_entriesDict[domain][key]);
            }
            finally
            {
                _writeLocker.ExitReadLock();
            }
        }

        public Guid? GetGuid(string domain, string key)
        {
            var value = GetValue(domain, key);
            return value == null ? null : Guid.Parse(value);
        }

        public void SetValue(string domain, string key, string value)
        {
            _writeLocker.EnterWriteLock();
            try
            {
                if (!_entriesDict.ContainsKey(domain))
                {
                    _entriesDict.Add(domain, new Dictionary<string, string>());
                }

                if (!_entriesDict[domain].ContainsKey(key))
                {
                    _entriesDict[domain].Add(key, value);

                    _dbContext.Entries.Add(new ConfigurationEntryModel()
                    {
                        Domain = domain,
                        Key = key, Value = value
                    });

                    _dbContext.SaveChanges();
                }
                else
                {
                    _entriesDict[domain][key] = value;
                }
            }
            catch (DbUpdateException ex)
            {
                _logger.LogError(ex, "Database operation failed.");
                _entriesDict[domain].Remove(key);
                throw new ApplicationException("Inner database operation failed.", innerException: ex);
            }
            finally
            {
                _writeLocker.EnterWriteLock();
            }
        }

        public void SetValue<TObject>(string domain, string key, TObject value)
        {
            var json = JsonConvert.SerializeObject(value);
            SetValue(domain, key, json);
        }

        public void SetValue(string domain, string key, int value)
        {
            SetValue(domain, key, Convert.ToString(value));
        }


        public void SetValue(string domain, string key, long value)
        {
            SetValue(domain, key, value.ToString());
        }

        public void SetValue(string domain, string key, Guid value)
        {
            SetValue(domain, key, value.ToString("N"));
        }
    }
}
